package fina2.dcs.auth;

import java.security.Principal;
import java.security.acl.Group;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.naming.InitialContext;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.apache.log4j.Logger;
import org.jboss.security.NestableGroup;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.callback.UsernamePasswordHandler;

import fina2.dcs.context.ContextHelper;
import fina2.dcs.security.RoleLocal;
import fina2.dcs.security.User;
import fina2.dcs.security.UserLocal;
import fina2.dcs.system.SysUserBankLocal;

public class FinaDcsLoginModule implements LoginModule {

	private Logger log = Logger.getLogger(FinaDcsLoginModule.class);
	private Subject subject;
	private CallbackHandler ch;

	private String username;
	private String password;
	private String USERNAME = null;
	private String PASSWORD = null;
	private String ROLE = "fina.dcs.user";
	private boolean loginOk = false;
	private List<String> perms = null;
	private Principal identity;
	private LoginContext lc;
	private InitialContext ic;
	private UserLocal ul;
	private User u = null;

	private SysUserBankLocal userBank = null;
	private RoleLocal rl = null;

	private boolean roleHasInternalPerm = false;
	private boolean roleHasExternalPerm = false;
	private boolean userInternal = false;
	private boolean userExternal = false;

	@Override
	public boolean abort() throws LoginException {
		log.info("Aborting...");
		subject.getPrincipals().remove(identity);
		return true;
	}

	public boolean commit() throws LoginException {
		log.info("Commiting....");
		Set<Principal> principalSet = subject.getPrincipals();
		principalSet.add(identity);
		Group[] roleSet = getRoleSet();
		for (int i = 0; i < roleSet.length; i++) {
			Group group = roleSet[i];
			String name = group.getName();
			Group subjectGroup = createGroup(name, principalSet);
			if (subjectGroup instanceof NestableGroup) {
				SimpleGroup sg = new SimpleGroup("Roles");
				subjectGroup.addMember(sg);
				subjectGroup = sg;
			}
			Enumeration<? extends Principal> en = group.members();
			while (en.hasMoreElements()) {
				Principal p = en.nextElement();
				subjectGroup.addMember(p);
			}
		}
		if (lc != null) {
			lc.logout();
		}

		return true;
	}

	@Override
	public void initialize(Subject subject, CallbackHandler ch, Map<String, ?> sharedState, Map<String, ?> options) {
		log.info("Initializing....");

		this.subject = subject;
		this.ch = ch;
		try {
			ic = ContextHelper.getInitialContext();
			rl = ContextHelper.injectRoleLocal(ic);
			userBank = ContextHelper.injectSysUserBankLocal(ic);
		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}

	}

	@Override
	public boolean login() throws LoginException {
		log.info("Trying to log in....");
		if (ch == null)
			throw new LoginException("Error initializing login module");
		Callback[] callbacks = new Callback[2];
		callbacks[0] = new NameCallback("username");
		callbacks[1] = new PasswordCallback("password", false);
		try {
			ch.handle(callbacks);
			username = ((NameCallback) callbacks[0]).getName();
			password = new String(((PasswordCallback) callbacks[1]).getPassword());
		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}
		((PasswordCallback) callbacks[1]).clearPassword();
		initCredentials();
		initialize();
		if (credentialsValid()) {
			loginOk = true;
			identity = new SimplePrincipal(username);
		}

		return loginOk;
	}

	public boolean logout() throws LoginException {
		log.info("Loggin out...");
		if (lc != null)
			lc.logout();
		return false;
	}

	public boolean credentialsValid() {
		boolean crValid = true;

		try {
			if (username == null || password == null) {
				log.info("Username/password is empty");
				crValid = false;
			}
			else if (username.trim().toLowerCase().equals("sa")) {
				crValid = (USERNAME.equals(username.toLowerCase()) && PASSWORD.equals(ul.hashPassword(password)));
				if(!crValid)
					log.info("Password is incorrect for user 'sa' ");
			}
			else {

				if (roleHasInternalPerm || roleHasExternalPerm || userInternal || userExternal) {
					if ((userExternal || roleHasExternalPerm) && ((!userInternal) && (!roleHasInternalPerm))) {
						if ((userBank.getUsersBanksSize(u.getId()) == 1) && ((USERNAME.equals(username) && PASSWORD.equals(ul.hashPassword(password))))) {
							crValid = true;
						} else {
							crValid = false;
							log.info("External User: User does not have exactly one Fi or username/password is incorrect");
						}
					} else if (userInternal || roleHasInternalPerm) {
						if ((userBank.getUsersBanksSize(u.getId()) > 0) && ((USERNAME.equals(username.toLowerCase()) && PASSWORD.equals(ul.hashPassword(password))))) {
							crValid = true;
						} else {
							log.info("Internal User: User does not have any Fi or username/password is incorrect");
							crValid = false;
						}
					}
					
				} else {
					log.info("User is not either external neither internal!");
					crValid = false;
				}

			}

		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
			crValid = false;
		}
		return crValid;

	}

	public void initialize() {
		try {

			perms = rl.loadPermissions(u.getId());
			roleHasInternalPerm = perms.contains("fina2.web.internal.user");
			roleHasExternalPerm = perms.contains("fina2.web.external.user");
			userInternal = ul.hasPermission(u.getId(), "fina2.web.internal.user");
			userExternal = ul.hasPermission(u.getId(), "fina2.web.external.user");
			if (!credentialsValid()) {
				lc = new LoginContext("client-login", new UsernamePasswordHandler(username, password.toCharArray()));
				lc.login();
			}

		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}
	}

	public Group[] getRoleSet() {
		Group roleGroups[] = new Group[1];
		roleGroups[0] = new SimpleGroup("Roles");
		String roles[] = getRoles();
		for (int i = 0; i < roles.length; i++) {
			SimpleGroup sg = new SimpleGroup(roles[i]);
			roleGroups[0].addMember(sg);
		}
		return roleGroups;
	}

	public String[] getRoles() {
		String roles[] = null;
		if (credentialsValid()) {
			roles = new String[] { ROLE };
		}
		return roles;
	}

	public Group createGroup(String name, Set principalSet) {
		Group role = null;
		Iterator<? extends Principal> iter = principalSet.iterator();
		while (iter.hasNext()) {
			Principal p = iter.next();
			if (!(p instanceof Group))
				continue;
			Group gr = (Group) p;
			if (gr.getName().equals(name)) {
				role = gr;
				break;
			}
		}
		if (role == null) {
			role = new NestableGroup(name);
			principalSet.add(role);
		}
		return role;
	}

	public void initCredentials() {
		try {
			ic = ContextHelper.getInitialContext();
			ul = ContextHelper.injectUserLocal(ic);

			u = ul.findByLoginPassword(username, ul.hashPassword(password));
			if (u != null) {
				USERNAME = u.getLogin().trim().toLowerCase();
				PASSWORD = u.getPassword().trim();
			}
		} catch (Exception ex) {
			log.error(ex.getMessage(), ex);
		}
	}
}
